<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <meta name="keywords" content="Hexo Theme Redefine">
    <meta name="description" content="Hexo Theme Redefine">
    <meta name="author" content="Sakura">
    
    <title>
        
            如何设计一个优雅的接口 |
        
        Sakura Blog
    </title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link rel="preconnect" href="https://evan.beee.top" crossorigin>
    
<link rel="stylesheet" href="/sakura-blog/css/style.css">

    <link rel="shortcut icon" href="/sakura-blog/images/bitbug_favicon.ico">
    
<link rel="stylesheet" href="/sakura-blog/css/fontawesome.min.css">

    
<link rel="stylesheet" href="/sakura-blog/css/brands.min.css">

    
<link rel="stylesheet" href="/sakura-blog/css/solid.min.css">

    
<link rel="stylesheet" href="/sakura-blog/css/regular.min.css">

    
    
    
    
<link rel="stylesheet" href="/sakura-blog/css/css2.css">

    <script id="hexo-configurations">
    let REDEFINE = window.REDEFINE || {};
    REDEFINE.hexo_config = {"hostname":"xindong_a.gitee.io","root":"/sakura-blog/","language":"zh-CN"};
    REDEFINE.theme_config = {"toc":{"enable":true,"number":false,"expand_all":true,"init_open":true},"style":{"primary_color":"#005080","avatar":"/images/avatar.jpg","favicon":"/images/bitbug_favicon.ico","article_img_align":"center","right_side_width":"210px","content_max_width":"1000px","nav_color":{"left":"#f78736","right":"#367df7","transparency":35},"hover":{"shadow":true,"scale":false},"first_screen":{"enable":true,"background_image":{"light":"https://evan.beee.top/img/wallhaven-wqery6-light.webp","dark":"https://evan.beee.top/img/wallhaven-wqery6-dark.webp"},"title_color":{"light":"#fff","dark":"#d1d1b6"},"description":"宁静致远"},"scroll":{"progress_bar":{"enable":true},"percent":{"enable":false}}},"local_search":{"enable":false,"preload":true},"code_block":{"copy":true,"style":"mac"},"pjax":{"enable":true},"lazyload":{"enable":true},"version":"0.4.6","friend_links":{"columns":2}};
    REDEFINE.language_ago = {"second":"%s 秒前","minute":"%s 分钟前","hour":"%s 小时前","day":"%s 天前","week":"%s 周前","month":"%s 个月前","year":"%s 年前"};
  </script>
<meta name="generator" content="Hexo 6.3.0"></head>


<body>
<div class="progress-bar-container">
    
        <span class="scroll-progress-bar"></span>
    

    
        <span class="pjax-progress-bar"></span>
        <span class="pjax-progress-icon">
            <i class="fa-solid fa-circle-notch fa-spin"></i>
        </span>
    
</div>


<main class="page-container">

    

    <div class="page-main-content">

        <div class="page-main-content-top">
            <header class="header-wrapper">
    
    <div class="header-content">
        <div class="left">
            
            <a class="logo-title" href="/">
                Sakura Blog
            </a>
        </div>

        <div class="right">
            <!-- PC -->
            <div class="pc">
                <ul class="menu-list">
                    
                        <li class="menu-item">
                            <!-- Menu -->
                            <a class="" 
                                href="/sakura-blog/" >
                                
                                    
                                        <i class="fa-regular fa-house"></i>
                                    
                                    首页
                                
                            </a>
                            <!-- Submenu -->
                            
                        </li>
                    
                        <li class="menu-item">
                            <!-- Menu -->
                            <a class="" 
                                href="/sakura-blog/archives" >
                                
                                    
                                        <i class="fa-regular fa-archive"></i>
                                    
                                    归档
                                
                            </a>
                            <!-- Submenu -->
                            
                        </li>
                    
                        <li class="menu-item">
                            <!-- Menu -->
                            <a class="" 
                                target="_blank" rel="noopener" href="https://status.evanluo.top/" >
                                
                                    
                                        <i class="fa-regular fa-chart-bar"></i>
                                    
                                    状态
                                
                            </a>
                            <!-- Submenu -->
                            
                        </li>
                    
                        <li class="menu-item">
                            <!-- Menu -->
                            <a class="has-dropdown" 
                                href="javascript:void(0);">
                                
                                    
                                        <i class="fa-regular fa-user"></i>
                                    
                                    关于&nbsp;<i class="fa-solid fa-chevron-down"></i>
                                
                            </a>
                            <!-- Submenu -->
                            
                                <ul class="sub-menu">
                                  
                                    <li>
                                      <a href="/sakura-blog/about">ME
                                      </a>
                                    </li>
                                  
                                    <li>
                                      <a target="_blank" rel="noopener" href="https://github.com/EvanNotFound/hexo-theme-redefine">GITHUB
                                      </a>
                                    </li>
                                  
                                    <li>
                                      <a target="_blank" rel="noopener" href="https://www.evanluo.top">BLOG
                                      </a>
                                    </li>
                                  
                                    <li>
                                      <a href="/sakura-blog/friends">FRIENDS
                                      </a>
                                    </li>
                                  
                                </ul>
                            
                        </li>
                    
                    
                </ul>
            </div>
            <!-- Mobile -->
            <div class="mobile">
                
                <div class="icon-item menu-bar">
                    <div class="menu-bar-middle"></div>
                </div>
            </div>
        </div>
    </div>

    <!-- Mobile drawer -->
    <div class="header-drawer">
        <ul class="drawer-menu-list">
            
                <li class="drawer-menu-item flex-center">
                    <a class="" 
                       href="/sakura-blog/" >
                         
                            
                                <i class="fa-regular fa-house"></i>
                            
                            首页
                        
                    </a>
                </li>
                <!-- Submenu -->
                
            
                <li class="drawer-menu-item flex-center">
                    <a class="" 
                       href="/sakura-blog/archives" >
                         
                            
                                <i class="fa-regular fa-archive"></i>
                            
                            归档
                        
                    </a>
                </li>
                <!-- Submenu -->
                
            
                <li class="drawer-menu-item flex-center">
                    <a class="" 
                       target="_blank" rel="noopener" href="https://status.evanluo.top/" >
                         
                            
                                <i class="fa-regular fa-chart-bar"></i>
                            
                            状态
                        
                    </a>
                </li>
                <!-- Submenu -->
                
            
                <li class="drawer-menu-item flex-center">
                    <a class="has-dropdown" 
                       href="javascript:void(0);">
                        
                            
                                <i class="fa-regular fa-user"></i>
                            
                            关于&nbsp;<i class="fa-solid fa-chevron-down"></i>
                        
                    </a>
                </li>
                <!-- Submenu -->
                          
                    
                        <li class="dropdown-item flex-center">
                            <a class="dropdown-item" href="/sakura-blog/about">ME</a>
                        </li>
                    
                        <li class="dropdown-item flex-center">
                            <a class="dropdown-item" target="_blank" rel="noopener" href="https://github.com/EvanNotFound/hexo-theme-redefine">GITHUB</a>
                        </li>
                    
                        <li class="dropdown-item flex-center">
                            <a class="dropdown-item" target="_blank" rel="noopener" href="https://www.evanluo.top">BLOG</a>
                        </li>
                    
                        <li class="dropdown-item flex-center">
                            <a class="dropdown-item" href="/sakura-blog/friends">FRIENDS</a>
                        </li>
                    
                
            
        </ul>
    </div>

    <div class="window-mask"></div>

</header>


        </div>

        <div class="page-main-content-middle">

            <div class="main-content">

                
                    <div class="fade-in-down-animation">
    <div class="post-page-container">
        <div class="article-content-container">
            <div class="article-title">
                <span class="title-hover-animation"><h1 style="font-size:2rem; font-weight: bold; margin: 10px 0;">如何设计一个优雅的接口</h1></span>
            </div>

            
                <div class="article-header">
                    <div class="avatar">
                        <img src="/sakura-blog/images/avatar.jpg">
                    </div>
                    <div class="info">
                        <div class="author">
                            <span class="name">Sakura</span>
                            
                                <span class="author-label">lol</span>
                            
                        </div>
                        <div class="meta-info">
                            <div class="article-meta-info">
    <span class="article-date article-meta-item">
        <i class="fa-regular fa-pen-fancy"></i>&nbsp;
        <span class="pc">2023-01-09 10:26:47</span>
        <span class="mobile">2023-01-09 10:26</span>
    </span>
    
    
        <span class="article-tags article-meta-item">
            <i class="fa-regular fa-tags"></i>&nbsp;
            <ul>
                
                    <li>
                        <a href="/sakura-blog/tags/%E7%AE%80%E7%AC%94/">简笔</a>&nbsp;
                    </li>
                
            </ul>
        </span>
    

    
    
    
    
        <span class="article-pv article-meta-item">
            <i class="fa-regular fa-eye"></i>&nbsp;<span id="busuanzi_value_page_pv"></span>
        </span>
    
</div>

                        </div>
                    </div>
                </div>
            

            <div class="article-content markdown-body">
                <h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>在实际工作中，我们需要经常跟第三方平台打交道，可能会对接第三方平台API接口，或者提供API接口给第三方平台调用。</p>
<p>那么问题来了，如果设计一个优雅的API接口，能够满足：安全性、可重复调用、稳定性、好定位问题等多方面需求？</p>
<p>今天跟大家一起聊聊设计API接口时，需要注意的一些地方，希望对你会有所帮助。</p>
<h3 id="1-签名"><a href="#1-签名" class="headerlink" title="1. 签名"></a>1. 签名</h3><p>为了防止API接口中的数据被篡改，很多时候我们需要对API接口做<code>签名</code>。</p>
<p>接口请求方将<code>请求参数</code>+<code>时间戳</code>+<code>密钥</code>拼接成一个字符串，然后通过<code>md5</code>等hash算法，生成一个前面sign。</p>
<p>然后在请求参数或者请求头中，增加sign参数，传递给API接口。</p>
<p>API接口的网关服务，获取到该sign值，然后用相同的请求参数 + 时间戳 + 密钥拼接成一个字符串，用相同的m5算法生成另外一个sign，对比两个sign值是否相等。</p>
<p>如果两个sign相等，则认为是有效请求，API接口的网关服务会将给请求转发给相应的业务系统。</p>
<p>如果两个sign不相等，则API接口的网关服务会直接返回签名错误。</p>
<p>问题来了：签名中为什么要加时间戳？</p>
<p>答：为了安全性考虑，防止同一次请求被反复利用，增加了密钥没破解的可能性，我们必须要对每次请求都设置一个合理的过期时间，比如：15分钟。</p>
<p>这样一次请求，在15分钟之内是有效的，超过15分钟，API接口的网关服务会返回超过有效期的异常提示。</p>
<p>目前生成签名中的密钥有两种形式：</p>
<p>一种是双方约定一个固定值privateKey。</p>
<p>另一种是API接口提供方给出AK&#x2F;SK两个值，双方约定用SK作为签名中的密钥。AK接口调用方作为header中的accessKey传递给API接口提供方，这样API接口提供方可以根据AK获取到SK，而生成新的sgin。</p>
<h3 id="2-加密"><a href="#2-加密" class="headerlink" title="2. 加密"></a>2. 加密</h3><p>有些时候，我们的API接口直接传递的非常重要的数据，比如：用户的银行卡号、转账金额、用户身份证等，如果将这些参数，直接明文，暴露到公网上是非常危险的事情。</p>
<p>由此，我们需要对数据进行<code>加密</code>。</p>
<p>目前使用比较多的是用<code>BASE64</code>加解密。</p>
<p>我们可以将所有的数据，安装一定的规律拼接成一个大的字符串，然后在加一个<code>密钥</code>，拼接到一起。</p>
<p>然后使用JDK1.8之后的Base64工具类处理，效果如下：</p>
<div class="highlight-container" data-rel="Tex"><figure class="iseeu highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">【加密前的数据】www.baidu.com</span><br><span class="line">【加密后的数据】d3d3LmJhaWR1LmNvbQ==</span><br></pre></td></tr></table></figure></div>

<p>为了安全性，使用Base64可以加密多次。</p>
<p>API接口的调用方在传递参数时，body中只有一个参数data，它就是base64之后的加密数据。</p>
<p>API接口的网关服务，在接收到data数据后，根据双方事先预定的密钥、加密算法、加密次数等，进行解密，并且反序列化出参数数据。</p>
<h3 id="3-ip白名单"><a href="#3-ip白名单" class="headerlink" title="3. ip白名单"></a>3. ip白名单</h3><p>为了进一步加强API接口的安全性，防止接口的签名或者加密被破解了，攻击者可以在自己的服务器上请求该接口。</p>
<p>需求限制请求<code>ip</code>，增加<code>ip白名单</code>。</p>
<p>只有在白名单中的ip地址，才能成功请求API接口，否则直接返回无访问权限。</p>
<p>ip白名单也可以加在API网关服务上。</p>
<p>但也要防止公司的内部应用服务器被攻破，这种情况也可以从内部服务器上发起API接口的请求。</p>
<p>这时候就需要增加web防火墙了，比如：ModSecurity等。</p>
<h3 id="4-限流"><a href="#4-限流" class="headerlink" title="4. 限流"></a>4. 限流</h3><p>如果你的API接口被第三方平台调用了，这就意味着着，调用频率是没法控制的。</p>
<p>第三方平台调用你的API接口时，如果并发量一下子太高，可能会导致你的API服务不可用，接口直接挂掉。</p>
<p>由此，必须要对API接口做<code>限流</code>。</p>
<p>限流方法有三种：</p>
<ol>
<li>对请求ip做限流：比如同一个ip，在一分钟内，对<code>API接口总的请求次数</code>，不能超过10000次。</li>
<li>对请求接口做限流：比如同一个ip，在一分钟内，对<code>指定的API接口</code>，请求次数不能超过2000次。</li>
<li>对请求用户做限流：比如同一个<code>AK/SK用户</code>，在一分钟内，对API接口总的请求次数，不能超过10000次。</li>
</ol>
<p>我们在实际工作中，可以通过<code>nginx</code>，<code>redis</code>或者<code>gateway</code>实现限流的功能。</p>
<h3 id="5-参数校验"><a href="#5-参数校验" class="headerlink" title="5. 参数校验"></a>5. 参数校验</h3><p>我们需要对API接口做<code>参数校验</code>，比如：校验必填字段是否为空，校验字段类型，校验字段长度，校验枚举值等等。</p>
<p>这样做可以拦截一些无效的请求。</p>
<p>比如在新增数据时，字段长度超过了数据字段的最大长度，数据库会直接报错。</p>
<p>但这种异常的请求，我们完全可以在API接口的前期进行识别，没有必要走到数据库保存数据那一步，浪费系统资源。</p>
<p>有些金额字段，本来是正数，但如果用户传入了负数，万一接口没做校验，可能会导致一些没必要的损失。</p>
<p>还有些状态字段，如果不做校验，用户如果传入了系统中不存在的枚举值，就会导致保存的数据异常。</p>
<p>由此可见，做参数校验是非常有必要的。</p>
<p>在Java中校验数据使用最多的是<code>hiberate</code>的<code>Validator</code>框架，它里面包含了@Null、@NotEmpty、@Size、@Max、@Min等注解。</p>
<p>用它们校验数据非常方便。</p>
<p>当然有些日期字段和枚举字段，可能需要通过自定义注解的方式实现参数校验。</p>
<h3 id="6-统一返回值"><a href="#6-统一返回值" class="headerlink" title="6. 统一返回值"></a>6. 统一返回值</h3><p>我之前调用过别人的API接口，正常返回数据是一种json格式，比如：</p>
<div class="highlight-container" data-rel="Tex"><figure class="iseeu highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    &quot;code&quot;:0,</span><br><span class="line">    &quot;message&quot;:null,</span><br><span class="line">    &quot;data&quot;:[&#123;&quot;id&quot;:123,&quot;name&quot;:&quot;abc&quot;&#125;]</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure></div>

<p>签名错误返回的json格式：</p>
<div class="highlight-container" data-rel="Tex"><figure class="iseeu highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    &quot;code&quot;:1001,</span><br><span class="line">    &quot;message&quot;:&quot;签名错误&quot;,</span><br><span class="line">    &quot;data&quot;:null</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>没有数据权限返回的json格式：</p>
<div class="highlight-container" data-rel="Tex"><figure class="iseeu highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    &quot;rt&quot;:10,</span><br><span class="line">    &quot;errorMgt&quot;:&quot;没有权限&quot;,</span><br><span class="line">    &quot;result&quot;:null</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>这种是比较坑的做法，返回值中有多种不同格式的返回数据，这样会导致对接方很难理解。</p>
<p>出现这种情况，可能是API网关定义了一直返回值结构，业务系统定义了另外一种返回值结构。如果是网关异常，则返回网关定义的返回值结构，如果是业务系统异常，则返回业务系统的返回值结构。</p>
<p>但这样会导致API接口出现不同的异常时，返回不同的返回值结构，非常不利于接口的维护。</p>
<p>其实这个问题我们可以在设计<code>API网关</code>时解决。</p>
<p>业务系统在出现异常时，抛出业务异常的RuntimeException，其中有个message字段定义异常信息。</p>
<p>所有的API接口都必须经过API网关，API网关捕获该业务异常，然后转换成统一的异常结构返回，这样能统一返回值结构。</p>
<h3 id="7-统一封装异常"><a href="#7-统一封装异常" class="headerlink" title="7. 统一封装异常"></a>7. 统一封装异常</h3><p>我们的API接口需要对<code>异常</code>进行统一处理。</p>
<p>不知道你有没有遇到过这种场景：有时候在API接口中，需要访问数据库，但表不存在，或者sql语句异常，就会直接把sql信息在API接口中直接返回。</p>
<p>返回值中包含了<code>异常堆栈信息</code>、<code>数据库信息</code>、<code>错误代码和行数</code>等信息。</p>
<p>如果直接把这些内容暴露给第三方平台，是很危险的事情。</p>
<p>有些不法分子，利用接口返回值中的这些信息，有可能会进行sql注入或者直接脱库，而对我们系统造成一定的损失。</p>
<p>因此非常有必要对API接口中的异常做统一处理，把异常转换成这样：</p>
<div class="highlight-container" data-rel="Tex"><figure class="iseeu highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    &quot;code&quot;:500,</span><br><span class="line">    &quot;message&quot;:&quot;服务器内部错误&quot;,</span><br><span class="line">    &quot;data&quot;:null</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>返回码<code>code</code>是<code>500</code>，返回信息<code>message</code>是<code>服务器内部异常</code>。</p>
<p>这样第三方平台就知道是API接口出现了内部问题，但不知道具体原因，他们可以找我们排查问题。</p>
<p>我们可以在内部的日志文件中，把堆栈信息、数据库信息、错误代码行数等信息，打印出来。</p>
<p>我们可以在<code>gateway</code>中对异常进行拦截，做统一封装，然后给第三方平台的是处理后没有敏感信息的错误信息。</p>
<h3 id="8-请求日志"><a href="#8-请求日志" class="headerlink" title="8. 请求日志"></a>8. 请求日志</h3><p>在第三方平台请求你的API接口时，接口的请求日志非常重要，通过它可以快速的分析和定位问题。</p>
<p>我们需要把API接口的请求url、请求参数、请求头、请求方式、响应数据和响应时间等，记录到日志文件中。</p>
<p>最好有<code>traceId</code>，可以通过它串联整个请求的日志，过滤多余的日志。</p>
<p>当然有些时候，请求日志不光是你们公司开发人员需要查看，第三方平台的用户也需要能查看接口的请求日志。</p>
<p>这时就需要把日志落地到数据库，比如：<code>mongodb</code>或者<code>elastic search</code>，然后做一个UI页面，给第三方平台的用户开通查看权限。这样他们就能在外网查看请求日志了，他们自己也能定位一部分问题。</p>
<h3 id="9-幂等设计"><a href="#9-幂等设计" class="headerlink" title="9. 幂等设计"></a>9. 幂等设计</h3><p>第三方平台极有可能在极短的时间内，请求我们接口多次，比如：在1秒内请求两次。有可能是他们业务系统有bug，或者在做接口调用失败重试，因此我们的API接口需要做<code>幂等设计</code>。</p>
<p>也就是说要支持在极短的时间内，第三方平台用相同的参数请求API接口多次，第一次请求数据库会新增数据，但第二次请求以后就不会新增数据，但也会返回成功。</p>
<p>这样做的目的是不会产生错误数据。</p>
<p>我们在日常工作中，可以通过在<code>数据库</code>中增加<code>唯一索引</code>，或者在<code>redis</code>保存<code>requestId</code>和请求参来保证接口幂等性。</p>
<p>对接口幂等性感兴趣的小伙伴，详见拙作《<a class="link" target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s?__biz=MzkwNjMwMTgzMQ==&mid=2247490307&idx=1&sn=b9eeb427c33cb171da6c3f11243a88f4&chksm=c0ebc3ebf79c4afd0d5a1851a975534b672d86c531d28c5933013140173e794f5f53e78a6765&token=751314179&lang=zh_CN&scene=21#wechat_redirect">高并发下如何保证接口的幂等性？<i class="fa-solid fa-up-right-from-square"></i></a>》，里面有非常详细的介绍</p>
<h3 id="10-限制记录条数"><a href="#10-限制记录条数" class="headerlink" title="10. 限制记录条数"></a>10. 限制记录条数</h3><p>对于对我提供的批量接口，一定要<code>限制请求的记录条数</code>。</p>
<p>如果请求的数据太多，很容易造成API<code>接口超时</code>等问题，让API接口变得不稳定。</p>
<p>通常情况下，建议一次请求中的参数，最多支持传入500条记录。</p>
<p>如果用户传入多余500条记录，则接口直接给出提示。</p>
<p>建议这个参数做成可配置的，并且要事先跟第三方平台协商好，避免上线后产生不必要的问题。</p>
<h3 id="11-压测"><a href="#11-压测" class="headerlink" title="11. 压测"></a>11. 压测</h3><p>上线前我们务必要对API接口做一下<code>压力测试</code>，知道各个接口的<code>qps</code>情况。</p>
<p>以便于我们能够更好的预估，需要部署多少服务器节点，对于API接口的稳定性至关重要。</p>
<p>之前虽说对API接口做了限流，但是实际上API接口是否能够达到限制的阀值，这是一个问号，如果不做压力测试，是有很大风险的。</p>
<p>比如：你API接口限流1秒只允许50次请求，但实际API接口只能处理30次请求，这样你的API接口也会处理不过来。</p>
<p>我们在工作中可以用<code>jmeter</code>或者<code>apache benc</code>对API接口做压力测试。</p>
<h3 id="12-异步处理"><a href="#12-异步处理" class="headerlink" title="12. 异步处理"></a>12. 异步处理</h3><p>一般的API接口的逻辑都是同步处理的，请求完之后立刻返回结果。</p>
<p>但有时候，我们的API接口里面的业务逻辑非常复杂，特别是有些批量接口，如果同步处理业务，耗时会非常长。</p>
<p>这种情况下，为了提升API接口的性能，我们可以改成<code>异步处理</code>。</p>
<p>在API接口中可以发送一条<code>mq消息</code>，然后直接返回成功。之后，有个专门的<code>mq消费者</code>去异步消费该消息，做业务逻辑处理。</p>
<p>直接异步处理的接口，第三方平台有两种方式获取到。</p>
<p>第一种方式是：我们<code>回调</code>第三方平台的接口，告知他们API接口的处理结果，很多支付接口就是这么玩的。</p>
<p>第二种方式是：第三方平台通过<code>轮询</code>调用我们另外一个查询状态的API接口，每隔一段时间查询一次状态，传入的参数是之前的那个API接口中的id集合。</p>
<h3 id="13-数据脱敏"><a href="#13-数据脱敏" class="headerlink" title="13. 数据脱敏"></a>13. 数据脱敏</h3><p>有时候第三方平台调用我们API接口时，获取的数据中有一部分是敏感数据，比如：用户手机号、银行卡号等等。</p>
<p>这样信息如果通过API接口直接保留到外网，是非常不安全的，很容易造成用户隐私数据泄露的问题。</p>
<p>这就需要对部分数据做<code>数据脱敏</code>了。</p>
<p>我们可以在返回的数据中，部分内容用<code>星号</code>代替。</p>
<p>已用户手机号为例：<code>182****887</code>。</p>
<p>这样即使数据被泄露了，也只泄露了一部分，不法分子拿到这份数据也没啥用。</p>
<h3 id="14-完整的接口文档"><a href="#14-完整的接口文档" class="headerlink" title="14. 完整的接口文档"></a>14. 完整的接口文档</h3><p>说实话，一份完整的API接口文档，在双方做接口对接时，可以减少很多沟通成本，让对方少走很多弯路。</p>
<p>接口文档中需要包含如下信息：</p>
<ol>
<li>接口地址</li>
<li>请求方式，比如：post或get</li>
<li>请求参数和字段介绍</li>
<li>返回值和字段介绍</li>
<li>返回码和错误信息</li>
<li>加密或签名示例</li>
<li>完整的请求demo</li>
<li>额外的说明，比如：开通ip白名单。</li>
</ol>
<p>接口文档中最好能够统一接口和字段名称的命名风格，比如都用<code>驼峰标识</code>命名。</p>
<p>接口地址中可以加一个版本号v1，比如：v1&#x2F;query&#x2F;getCategory，这样以后接口有很大的变动，可以非常方便升级版本。</p>
<p>统一字段的类型和长度，比如：id字段用Long类型，长度规定20。status字段用int类型，长度固定2等。</p>
<p>统一时间格式字段，比如：time用String类型，格式为：yyyy-MM-dd HH:mm:ss。</p>
<p>接口文档中写明AK&#x2F;SK和域名，找某某单独提供等。</p>

            </div>

            
                <div class="post-copyright-info">
                    <div class="article-copyright-info-container">
    <ul>
        <li>本文标题：如何设计一个优雅的接口</li>
        <li>本文作者：Sakura</li>
        <li>创建时间：2023-01-09 10:26:47</li>
        <li>
            本文链接：https://xindong_a.gitee.io/sakura-blog/2023/01/09/如何写出一个优雅的接口/
        </li>
        <li>
            版权声明：本博客所有文章除特别声明外，均采用 <a class="license" target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh">BY-NC-SA</a> 许可协议。转载请注明出处！
        </li>
    </ul>
</div>

                </div>
            

            
                <ul class="post-tags-box">
                    
                        <li class="tag-item">
                            <a href="/sakura-blog/tags/%E7%AE%80%E7%AC%94/">#简笔</a>&nbsp;
                        </li>
                    
                </ul>
            

            
                <div class="article-nav">
                    
                        <div class="article-prev">
                            <a class="prev"
                            rel="prev"
                            href="/sakura-blog/2023/01/09/%E7%BC%96%E7%A8%8B%E5%AE%9D%E8%97%8F%E8%80%81%E5%B8%88/"
                            >
                                <span class="left arrow-icon flex-center">
                                    <i class="fa-solid fa-chevron-left"></i>
                                </span>
                                <span class="title flex-center">
                                    <span class="post-nav-title-item">编程宝藏老师</span>
                                    <span class="post-nav-item">上一篇</span>
                                </span>
                            </a>
                        </div>
                    
                    
                        <div class="article-next">
                            <a class="next"
                            rel="next"
                            href="/sakura-blog/2023/01/09/%E5%87%A0%E4%B8%AA%E7%AE%80%E5%8D%95%E5%A5%BD%E7%94%A8%E7%9A%84SQL%E8%AF%AD%E6%B3%95/"
                            >
                                <span class="title flex-center">
                                    <span class="post-nav-title-item">几个简单好用的SQL语法</span>
                                    <span class="post-nav-item">下一篇</span>
                                </span>
                                <span class="right arrow-icon flex-center">
                                    <i class="fa-solid fa-chevron-right"></i>
                                </span>
                            </a>
                        </div>
                    
                </div>
            

            
        </div>

        
            <div class="toc-content-container">
                <div class="post-toc-wrap">
    <div class="post-toc">
        <div style="font-size: 1.3rem;margin-top: 0; margin-bottom: 0.8rem; transition-duration: 0.1s;"><i class="fa-solid fa-list"></i> <strong>目录</strong></div>
        <ol class="nav"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%89%8D%E8%A8%80"><span class="nav-text">前言</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-%E7%AD%BE%E5%90%8D"><span class="nav-text">1. 签名</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-%E5%8A%A0%E5%AF%86"><span class="nav-text">2. 加密</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-ip%E7%99%BD%E5%90%8D%E5%8D%95"><span class="nav-text">3. ip白名单</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#4-%E9%99%90%E6%B5%81"><span class="nav-text">4. 限流</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#5-%E5%8F%82%E6%95%B0%E6%A0%A1%E9%AA%8C"><span class="nav-text">5. 参数校验</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#6-%E7%BB%9F%E4%B8%80%E8%BF%94%E5%9B%9E%E5%80%BC"><span class="nav-text">6. 统一返回值</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#7-%E7%BB%9F%E4%B8%80%E5%B0%81%E8%A3%85%E5%BC%82%E5%B8%B8"><span class="nav-text">7. 统一封装异常</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#8-%E8%AF%B7%E6%B1%82%E6%97%A5%E5%BF%97"><span class="nav-text">8. 请求日志</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#9-%E5%B9%82%E7%AD%89%E8%AE%BE%E8%AE%A1"><span class="nav-text">9. 幂等设计</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#10-%E9%99%90%E5%88%B6%E8%AE%B0%E5%BD%95%E6%9D%A1%E6%95%B0"><span class="nav-text">10. 限制记录条数</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#11-%E5%8E%8B%E6%B5%8B"><span class="nav-text">11. 压测</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#12-%E5%BC%82%E6%AD%A5%E5%A4%84%E7%90%86"><span class="nav-text">12. 异步处理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#13-%E6%95%B0%E6%8D%AE%E8%84%B1%E6%95%8F"><span class="nav-text">13. 数据脱敏</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#14-%E5%AE%8C%E6%95%B4%E7%9A%84%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A3"><span class="nav-text">14. 完整的接口文档</span></a></li></ol>
    </div>
</div>
            </div>
        
    </div>
</div>


                

            </div>



        </div>

        <div class="page-main-content-bottom">
            <footer class="footer">
    <div class="info-container">
        <div class="copyright-info info-item">
            &copy;
            
              <span>2023</span>
              -
            
            2023&nbsp;<i class="fa-solid fa-heart icon-animate"></i>&nbsp;<a href="/">Sakura. 版权所有.</a>
        </div>
        
            <script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
            <div class="website-count info-item">
                
                    <span id="busuanzi_container_site_uv">
                        访问人数&nbsp;<span id="busuanzi_value_site_uv"></span>&ensp;
                    </span>
                
                
                    <span id="busuanzi_container_site_pv">
                        总访问量&nbsp;<span id="busuanzi_value_site_pv"></span>
                    </span>
                
            </div>
        
        <div class="theme-info info-item">
            由 <a target="_blank" href="https://hexo.io">Hexo</a> 驱动&nbsp;|&nbsp;主题&nbsp;<a class="theme-version" target="_blank" href="https://github.com/EvanNotFound/hexo-theme-redefine">Redefine v0.4.6</a>
        </div>
        
        
        <script async data-pjax defer>
            function odometer_init(){
                    let el = document.getElementsByClassName('odometer');
                    for (i = 0; i < el.length; i++) {
                        od = new Odometer({
                            el: el[i],
                            format: '( ddd).dd',
                            duration: 200
                        });
                    }
            }
            odometer_init();
        </script>
        <div id="start_time_div" style="display:none">
            2023/1/4 12:00:00
        </div>
        
            <div>
                博客已运行 <span class="odometer" id="runtime_days" ></span> 天 <span class="odometer" id="runtime_hours"></span> 小时 <span class="odometer" id="runtime_minutes"></span> 分钟 <span class="odometer" id="runtime_seconds"></span> 秒
            </div>
        
        
        
    </div>  
</footer>
        </div>
    </div>

    
        <div class="post-tools">
            <div class="post-tools-container">
    <ul class="tools-list">
        <!-- TOC aside toggle -->
        
            <li class="tools-item page-aside-toggle">
                <i class="fa-regular fa-outdent"></i>
            </li>
        

        <!-- go comment -->
        
    </ul>
</div>

        </div>
    

    <div class="right-bottom-side-tools">
        <div class="side-tools-container">
    <ul class="side-tools-list">
        <li class="tools-item tool-font-adjust-plus flex-center">
            <i class="fa-solid fa-magnifying-glass-plus"></i>
        </li>

        <li class="tools-item tool-font-adjust-minus flex-center">
            <i class="fa-solid fa-magnifying-glass-minus"></i>
        </li>

        <li class="tools-item tool-expand-width flex-center">
            <i class="fa-solid fa-left-right"></i>
        </li>

        <li class="tools-item tool-dark-light-toggle flex-center">
            <i class="fa-solid fa-moon"></i>
        </li>

        <!-- rss -->
        

        
            <li class="tools-item tool-scroll-to-top flex-center">
                <i class="fa-solid fa-arrow-up"></i>
            </li>
        

        <li class="tools-item tool-scroll-to-bottom flex-center">
            <i class="fa-solid fa-arrow-down"></i>
        </li>
    </ul>

    <ul class="exposed-tools-list">
        <li class="tools-item tool-toggle-show flex-center">
            <i class="fa-solid fa-cog fa-spin"></i>
        </li>
        
    </ul>
</div>

    </div>

    <div class="image-viewer-container">
    <img src="">
</div>


    

</main>




<script src="/sakura-blog/js/utils.js"></script>

<script src="/sakura-blog/js/main.js"></script>

<script src="/sakura-blog/js/header-shrink.js"></script>

<script src="/sakura-blog/js/back2top.js"></script>

<script src="/sakura-blog/js/dark-light-toggle.js"></script>





    
<script src="/sakura-blog/js/code-copy.js"></script>




    
<script src="/sakura-blog/js/lazyload.js"></script>




    
<script src="/sakura-blog/js/runtime.js"></script>

    
<script src="/sakura-blog/js/odometer.min.js"></script>

    
<link rel="stylesheet" href="/sakura-blog/css/odometer-theme-minimal.css">



<div class="post-scripts pjax">
    
        
<script src="/sakura-blog/js/toc-toggle.js"></script>

<script src="/sakura-blog/js/libs/anime.min.js"></script>

<script src="/sakura-blog/js/toc.js"></script>

    
</div>


    
<script src="/sakura-blog/js/libs/pjax.min.js"></script>

<script>
    window.addEventListener('DOMContentLoaded', () => {
        window.pjax = new Pjax({
            selectors: [
                'head title',
                '.page-container',
                '.pjax'
            ],
            history: true,
            debug: false,
            cacheBust: false,
            timeout: 0,
            analytics: false,
            currentUrlFullReload: false,
            scrollRestoration: false,
            // scrollTo: true,
        });

        document.addEventListener('pjax:send', () => {
            REDEFINE.utils.pjaxProgressBarStart();
        });

        document.addEventListener('pjax:complete', () => {
            REDEFINE.utils.pjaxProgressBarEnd();
            window.pjax.executeScripts(document.querySelectorAll('script[data-pjax], .pjax script'));
            REDEFINE.refresh();
        });
    });
</script>



</body>
</html>
